package com.hrsaas.ext.spider.core;

import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import com.hrsaas.ext.spider.core.url.ParametersElement;
import com.hrsaas.ext.spider.core.url.URL;
import com.hrsaas.ext.spider.core.url.URLBuilder;
import com.hrsaas.ext.spider.util.Assert;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;

/**
 * <p>
 * 功能：请求参数实体对象
 * <p>
 * 
 * @author wangxiaoliang@kanzhun.com
 *
 *         2018年8月1日
 */
public abstract class RequestBuilder implements HttpRequestBuilder {

	private URLBuilder url = new URLBuilder();
	final DefaultHttpHeaders headers = new DefaultHttpHeaders(false);
	private final HttpMethod method;
	private HttpVersion version = HttpVersion.HTTP_1_1;
	protected CookieStore store;
	Duration timeout;
	private final ByteBufAllocator alloc;
	protected boolean noAggregate;
	private ByteBuf body;

	RequestBuilder(HttpMethod method, ByteBufAllocator alloc) {
		this.method = method;
		this.alloc = alloc;
	}

	@Override
	public RequestBuilder setTimeout(Duration timeout) {
		if (timeout != null && timeout.toMillis() <= 0) {
			throw new IllegalArgumentException("Cannot set timeout to <= 0");
		}
		this.timeout = timeout;
		return this;
	}

	@Override
	public RequestBuilder setProtocol(String protocol) {
		this.url.setProtocol(protocol);
		return this;
	}

	@Override
	public RequestBuilder setPath(String path) {
		this.url.setPath(path);
		return this;
	}

	@Override
	public RequestBuilder setPort(int port) {
		this.url.setPort(port);
		return this;
	}

	@Override
	public RequestBuilder setHost(String host) {
		this.url.setHost(host);
		return this;
	}

	@Override
	public <T> RequestBuilder addHeaders(HttpHeaders headers) {
		for (Entry<String, String> entry : headers) {
			this.headers.add(entry.getKey(), entry.getValue());
		}
		return this;
	}
	
	@Override
	public <T> RequestBuilder addHeader(String key, String value) {
		headers.set(key, value);
		return this;
	}
	
	@Override
	public RequestBuilder addReferer(String referer) {
		headers.add(HttpHeaderNames.REFERER.toString(),referer);
		return this;
	}
	
	@Override
	public RequestBuilder addUserAgent(String useragent) {
		headers.add(HttpHeaderNames.USER_AGENT.toString(),useragent);
		return this;
	}
	
	/**
	 * 添加请求参数
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	public HttpRequestBuilder addQueryPair(String key, String value) {
		url.addQueryPair(new ParametersElement(key, value));
		return this;
	}
	
	URL getURL() {
		return url.create();
	}
	
	HttpHeaders getHeaders() {
		return this.headers;
	}
	
	private boolean noDateHeader;

	@Override
	public RequestBuilder noDateHeader() {
		noDateHeader = true;
		return this;
	}

	private boolean noConnectionHeader;

	@Override
	public RequestBuilder noConnectionHeader() {
		noConnectionHeader = true;
		return this;
	}

	private boolean noHostHeader;

	@Override
	public RequestBuilder noHostHeader() {
		noHostHeader = true;
		return this;
	}

	@Override
	public RequestBuilder addCookieStore(CookieStore store) {
		this.store = store;
		return this;
	}

	ChunkedContent chunkedContent() {
		return new ChunkedContent() {
			@Override
			public Object nextChunk(int callCount) {
				return null;
			}
		};
	}

	protected HttpRequest build(URL u) {
		if (u == null) {
			throw new IllegalStateException("URL not set");
		}
		if (u.getHost() == null) {
			throw new IllegalStateException("URL host not set: " + u);
		}
		String uri = u.getPathAndQuery();
		HttpMethod mth = HttpMethod.valueOf(method.name());
		DefaultHttpRequest h = body == null ? new DefaultHttpRequest(version, mth, uri)
				: new DefaultFullHttpRequest(version, mth, uri, body);
		for (Entry<String, String> e : headers) {
			h.headers().add(e.getKey(), e.getValue());
		}
		if (!noHostHeader) {
			h.headers().add(HttpHeaderNames.HOST, u.getHost());
		}
		if (!h.headers().contains(HttpHeaderNames.CONNECTION) && !noConnectionHeader) {
			h.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
		}
		if (noDateHeader) {
			h.headers().add(HttpHeaderNames.DATE, ZonedDateTime.now().toLocalDate());
		}
		if (store != null) {
			store.decorateAsync(h);
		}
		return h;
	}

	@Override
	public HttpRequestBuilder setBody(Object bodyObject) {
		try {
			Assert.notNull("body", bodyObject);
			this.body = Unpooled.wrappedBuffer(((String) bodyObject).getBytes("UTF-8"));
			if (bodyObject instanceof String) {
				addHeader(HttpHeaderNames.CONTENT_TYPE.toString(),
						HttpHeaderValues.APPLICATION_OCTET_STREAM.toString());
				addHeader(HttpHeaderNames.CONTENT_LENGTH.toString(), 
						String.valueOf((long) body.readableBytes()));
			} else if (bodyObject instanceof ByteBuf) {
				this.body = (ByteBuf) bodyObject;
			} else {
				this.body = newByteBuf();
				addHeader(HttpHeaderNames.CONTENT_LENGTH.toString(),
						HttpHeaderValues.APPLICATION_OCTET_STREAM.toString());
				addHeader(HttpHeaderNames.CONTENT_TYPE.toString(),
						String.valueOf(this.body.readableBytes()));
			}
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
		return this;
	}
	
	protected final List<Receiver<State<?>>> any = new LinkedList<>();

	protected ByteBuf newByteBuf() {
		return alloc.buffer();
	}

	@Override
	public HttpRequestBuilder onEvent(Receiver<State<?>> r) {
		any.add(r);
		return this;
	}

	protected final List<HandlerEntry<?>> handlers = new LinkedList<>();

	@Override
	@SuppressWarnings("unchecked")
	public <T> HttpRequestBuilder on(Class<? extends State<T>> event, Receiver<T> r) {
		HandlerEntry<T> h = null;
		for (HandlerEntry<?> e : handlers) {
			if (e.state.equals(event)) {
				h = (HandlerEntry<T>) e;
				break;
			}
		}
		if (h == null) {
			h = new HandlerEntry<>(event);
			handlers.add(h);
		}
		h.add(r);
		return this;
	}

	@Override
	public HttpRequestBuilder setURL(String url) {
		this.url = new URLBuilder(url);
		return this;
	}

	@Override
	public HttpRequestBuilder dontAggregateResponse() {
		this.noAggregate = false;
		return this;
	}

}
